package com.gitee.huanminabc.multimedia.opencv;

import nu.pattern.OpenCV;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class OpencvImageUtil {
    static {
        //一起加载
        OpenCV.loadShared();
    }

    //获取图片的宽高
    public static int[] GeWidthAndHeight(String inputPath) {
        Mat imread = Imgcodecs.imread(inputPath);
        int width = imread.width();
        int height = imread.height();
        // 释放资源
        imread.release();
        return new int[]{width, height};
    }


    // Opencv 将图片转为灰度图
    public static void convertToGray(String inputPath, String outputPath) {
        //读取图片
        Mat src = Imgcodecs.imread(inputPath);
        //创建一个Mat对象,用于存储灰度图
        Mat dst = new Mat();
        //将图片转为灰度图
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY);
        //保存图片
        Imgcodecs.imwrite(outputPath, dst);
        // 释放资源
        src.release();
        dst.release();
    }

    // Opencv 将图片转为黑白图
    public static void convertToBinary(String inputPath, String outputPath) {
        //读取图片
        Mat src = Imgcodecs.imread(inputPath);
        //创建一个Mat对象,用于存储灰度图
        Mat dst = new Mat();
        //将图片转为灰度图
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_BGR2GRAY);
        //将图片转为黑白图
        Imgproc.threshold(dst, dst, 100, 255, Imgproc.THRESH_BINARY);
        //保存图片
        Imgcodecs.imwrite(outputPath, dst);
        // 释放资源
        src.release();
        dst.release();



    }

    //比较两张图片是否相同,大于0.9我们就认为是相同的
    //直方图就是长条形状的图, 把图片分割成256份,然后比较每一份的像素值,如果相同就是1,不同就是0,最后求和
    //这样就可以比较两张图片的相似度, 用于图片去重
    public static boolean compareImage(String inputPath1, String inputPath2) {
        //读取图片
        Mat src1 = Imgcodecs.imread(inputPath1);
        Mat src2 = Imgcodecs.imread(inputPath2);
        //创建一个Mat对象,用于存储灰度图
        Mat dst1 = new Mat();
        Mat dst2 = new Mat();
        //将图片转为灰度图
        Imgproc.cvtColor(src1, dst1, Imgproc.COLOR_BGR2GRAY);
        Imgproc.cvtColor(src2, dst2, Imgproc.COLOR_BGR2GRAY);
        //创建一个Mat对象,用于存储直方图
        Mat hist1 = new Mat();
        Mat hist2 = new Mat();
        //计算直方图
        Imgproc.calcHist(Arrays.asList(dst1), new MatOfInt(0), new Mat(), hist1, new MatOfInt(256), new MatOfFloat(0, 256));
        Imgproc.calcHist(Arrays.asList(dst2), new MatOfInt(0), new Mat(), hist2, new MatOfInt(256), new MatOfFloat(0, 256));
        //比较直方图
        double res = Imgproc.compareHist(hist1, hist2, Imgproc.HISTCMP_CORREL);
        // 释放资源
        src1.release();
        src2.release();
        dst1.release();
        dst2.release();

        return res > 0.9;
    }





    // 均值滤波
    public static void blur(Mat src, String outputPath) {
        Mat blur = new Mat();
        Imgproc.blur(src, blur, new Size(5, 5));
        //保存图片
        Imgcodecs.imwrite(outputPath, blur);
        // 释放资源
        src.release();
        blur.release();
    }



    //组合滤波
    public static void combineBlur(String inputPath, String outputPath, Map<String,Integer> radius) {

        //除了双边滤波,其他的滤波都是0~100之间,越高越模糊 0是不模糊
        for (Map.Entry<String, Integer> stringIntegerEntry : radius.entrySet()) {
            String key = stringIntegerEntry.getKey();
            Integer value = stringIntegerEntry.getValue();
            if (value < 0 || value > 100) {
                throw new RuntimeException("radius参数只能在0~100之间");
            }
        }
        //如果是双边滤波,则value是0~30之间,越高越模糊 0是不模糊
        if (radius.containsKey("bilateralFilter")) {
            Integer value = radius.get("bilateralFilter");
            if (value < 0 || value > 30) {
                throw new RuntimeException("bilateralFilter滤波器radius参数只能在0~30之间");
            }
        }

        //读取图片
        Mat src = Imgcodecs.imread(inputPath);
        for (Map.Entry<String, Integer> stringIntegerEntry : radius.entrySet()) {
            String key = stringIntegerEntry.getKey();
            Integer value = stringIntegerEntry.getValue();
            //转换为奇数
            if (value % 2 == 0) {
                value++;
            }
            Mat dst = new Mat();
            switch (key){
                case "gaussianBlur": //高斯滤波
                    Imgproc.GaussianBlur(src, dst, new Size(value, value), 0);
                    break;
                case "blur": //均值滤波
                    Imgproc.blur(src, dst, new Size(value,value));
                    break;
                case "medianBlur": //中值滤波
                    //中值滤波是一种非线性滤波方法，它取滤波器内核区域下所有像素的中值来替代中心像素的值。这种方法对于去除椒盐噪声（salt-and-pepper noise）特别有效，而且能够较好地保护图像的边缘信息。
                    Imgproc.medianBlur(src, dst, value);
                    break;
                case "bilateralFilter": //双边滤波
                    //平滑图像的同时保留边缘信息，因此常用于需要保持图像细节和纹理的应用中。
                    Imgproc.bilateralFilter(src, dst,0,80, value);
                    break;
                default:
                    throw new RuntimeException("不支持的滤波类型");
            }
            src.release();
            src = dst;//将上一次的结果赋值给src,这样就可以连续滤波

        }

        //保存图片
        Imgcodecs.imwrite(outputPath, src);
        // 释放资源
        src.release();

    }





    //将图像转换为指定格式,自动识别后缀
    public static void convertTo(Mat src, String outputPath, int type) {
        Mat dst = new Mat();
        Imgproc.cvtColor(src, dst, type);
        //保存图片
        Imgcodecs.imwrite(outputPath, dst);
        // 释放资源
        src.release();
        dst.release();
    }


    //抠图
    //zys是一个多边形的点的集合 ,和ps中的钢笔工具一样 前一个点和后一个点连起来 这样画出来的就是一个多边形
    public static void cutImage(String inputPath, String outputPath, List<Point> zys) {
        //判断outputPath是否是png格式
        if (!outputPath.endsWith(".png")) {
            throw new RuntimeException("outputPath后缀必须是png格式");
        }
        // 读取图像
        Mat src = Imgcodecs.imread(inputPath);

        //检测是否支持透明度
        if (src.channels() != 4) {
            //转换为4通道
            Mat temp = new Mat();
            Imgproc.cvtColor(src, temp, Imgproc.COLOR_BGR2BGRA);
            src.release();
            src = temp;
        }

        // 创建一个空白的mask，大小和原图一样，单通道，8位无符号整数类型
        Mat mask = Mat.zeros(src.size(), CvType.CV_8UC1);
        // 将Point的List转换为MatOfPoint
        MatOfPoint contour = new MatOfPoint();
        contour.fromList(zys);
        // 将MatOfPoint转换为MatOfPoint的List
        List<MatOfPoint> contours = new ArrayList<>();
        contours.add(contour); // 只有一个轮廓
        // 绘制轮廓到mask上，填充多边形
        Imgproc.fillPoly(mask, contours, new Scalar(255));
        // 创建一个用于存储结果的Mat对象,透明背景 ,4通道
        Mat cropped = new Mat(src.size(), CvType.CV_8UC4, new Scalar(0, 0, 0, 0));

        src.copyTo(cropped, mask);


        // 保存截取后的图像
        Imgcodecs.imwrite(outputPath, cropped);

        // 释放资源
        src.release();
        mask.release();
        cropped.release();
    }


    //将一个图合并到另一个图上
    public static void mergeImage(String backgroundImage, String overlayImage, String outputPath) {
        // 读取背景图和叠加图
        Mat background = Imgcodecs.imread(backgroundImage);
        // 确保以包含alpha通道的方式加载前景图（IMREAD_UNCHANGED 标志）
        Mat overlay = Imgcodecs.imread(overlayImage, Imgcodecs.IMREAD_UNCHANGED);

        // 检查图像文件是否正确加载
        if (background.empty() || overlay.empty()) {
            System.err.println("无法读取其中一个图像文件。");
            return;
        }
        //判断背景图和前景图是否一样大, 这里一般情况必须是一样大的,否则合并的时候位置会不对,下面做了简单的处理,但是会拉伸前景图,一般还是要保持一样大
        if (background.width() != overlay.width() || background.height() != overlay.height()) {
            //将前景图调整为与背景图相同的大小
            Imgproc.resize(overlay, overlay, background.size());
        }
        // 创建一个用于存储合并后图像的Mat对象，大小和类型与背景图相同
        Mat output = new Mat(background.size(), background.type());

        // 初始化输出图像为背景图像
        background.copyTo(output);

        // 遍历overlay图像的每个像素
        for (int y = 0; y < overlay.rows(); y++) {
            for (int x = 0; x < overlay.cols(); x++) {
                // 获取overlay图像当前像素的每个通道的值
                double[] overlayPixel = overlay.get(y, x);

                // 假设overlay图像是BGRA（蓝色、绿色、红色和Alpha透明度）
                double blue = overlayPixel[0];
                double green = overlayPixel[1];
                double red = overlayPixel[2];
                double alpha = overlayPixel[3] / 255.0; // 将Alpha值从0-255标准化到0-1

                // 如果Alpha为0，表示完全透明，无需更改背景图像
                if (alpha > 0) {
                    // 获取背景图像当前位置的像素值
                    double[] backgroundPixel = background.get(y, x);

                    // 根据overlay的透明度来合并像素值
                    double outputBlue = blue * alpha + backgroundPixel[0] * (1.0 - alpha);
                    double outputGreen = green * alpha + backgroundPixel[1] * (1.0 - alpha);
                    double outputRed = red * alpha + backgroundPixel[2] * (1.0 - alpha);

                    // 设置输出图像在相应位置的像素值
                    output.put(y, x, new double[]{outputBlue, outputGreen, outputRed});
                }
            }
        }

        // 保存合并后的图像
        Imgcodecs.imwrite(outputPath, output);

        // 释放资源
        background.release();
        overlay.release();
        output.release();

    }






}
